""" Basic Poll Example for Fontys SOT Course

This example is the basis for your exploration in Service Oriented Technology. We will make many changes.

Changes:
1.0.0: This version uses HTTP GET and will reply with XML responses.
"""
from flask import Flask
from flask import Response
from flask import render_template
from flask import abort

app = Flask(__name__)

__AUTHOR__ = "Caixia & Roy Berkeveld"
__VERSION__ = "1.0.0"  # Check out Semantic Versioning to decide how to change the version.
__MAJOR__ = __VERSION__.split(".")[0]  # We use this in the API URL. Major versions are not backwards-compatible.


# First we define the data model of our application.
# This is currently stored in process memory, which is fine for static data.

class Poll(object):
    """ The data structure used to represent a single poll """

    def __init__(self, poll_id, question):
        self.poll_id = poll_id
        self.question = question
        self.options = dict()
        self.votes = set() #  A set cannot contain duplicates

    def add_option(self, description):
        option_id = max(self.options.keys(), default=0) + 1
        self.options[option_id] = PollOption(option_id, description, parent=self)

    def to_template(self):
        return dict(
            poll_id=self.poll_id,
            question=self.question,
            options=[self.options[option].to_template() for option in self.options])


class PollOption(object):
    """ The data structure used to represent each poll option """

    def __init__(self, option_id, description, parent=None):
        self.option_id = option_id
        self.description = description
        self.parent = parent
        self.votes = set()  # A set cannot contain duplicates

    def vote_count(self):
        return len(self.votes)

    def to_template(self):
        return dict(
            option_id=self.option_id,
            description=self.description,
            vote_count=self.vote_count())


db = dict()  # A dictionary maps keys to values, in this case poll_ids to their class objects.


def add_poll(question):
    """ Adds a new poll to the database """
    poll_id = max(db.keys(), default=0) + 1
    db[poll_id] = Poll(poll_id, question)
    return poll_id


def init_db():
    """ Initialise the database with an example. """
    global db  # Get global db into our local namespace so we can modify it
    db = dict()  # Re-initialize db
    poll_id = add_poll('What is your CPU Architecture?')
    db[poll_id].add_option('X86 (32-bit OS)')
    db[poll_id].add_option('AMD64 (64-bit PC/Intel Mac)')
    db[poll_id].add_option('ARM64 (Apple Silicon)')


init_db()  # Don't forget to call the setup function!


# Next we define the routes, or the HTTP API of our application

@app.route(f"/v{__MAJOR__}/get/<int:poll_id>/")
def get_poll(poll_id):
    if poll_id not in db: abort(404)
    xml = render_template('poll.xml',
                          poll=db[poll_id].to_template(),
                          mimetype='text/xml')
    return Response(xml, mimetype='text/xml')


@app.route(f"/v{__MAJOR__}/vote/<int:poll_id>/<int:option_id>/<string:voter_name>")
def poll_vote(poll_id, option_id, voter_name):
    if poll_id not in db: abort(404)
    if option_id not in db[poll_id].options: abort(404)
    if voter_name in db[poll_id].votes: abort(403)

    # Register the vote
    db[poll_id].votes.add(voter_name) # to prevent double votes
    db[poll_id].options[option_id].votes.add(voter_name) # for counting option votes

    xml = render_template('vote.xml',
                          poll=db[poll_id].to_template(),
                          option_id=option_id,
                          voter_name=voter_name,
                          mimetype='text/xml')
    return Response(xml, mimetype='text/xml')


@app.route(f"/v{__MAJOR__}/result/<int:poll_id>")
def poll_result(poll_id):
    if poll_id not in db: abort(404)
    xml = render_template('result.xml',
                          poll=db[poll_id].to_template(),
                          mimetype='text/xml')
    return Response(xml, mimetype='text/xml')


# $ flask routes
# Endpoint     Methods  Rule
# -----------  -------  ----------------------------------------------------------
# get_poll     GET      /v1/get/<int:poll_id>/
# poll_result  GET      /v1/result/<int:poll_id>
# poll_vote    GET      /v1/vote/<int:poll_id>/<int:option_id>/<string:voter_name>
# static       GET      /static/<path:filename>
